/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.hamacekj.dotaznikserver;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.management.ManagementFactory;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import javax.imageio.IIOException;

/**
 * Zámek realizovaný pomocným souborem. V zamknutém stavu je vytvořený zamčený soubor (způsob zamčení souboru je závislé na platformě).
 * Tento zámek nelze získat dvakrát na stejném souboru a to ani z jiných procesů. Zamykací soubor bude při zamknutí
 * smazán. A znovuvytvořen s prázdným obsahem pro potřeby zámku. Při zamknutí se přidává ShutdownHook, který při ukončení aplikace automaticky
 * odemyká zámek.
 * @author Jan Hamáček
 */
public class SimpleFileLock {
    /**
     * Zamykací soubor bude DotaznikServer.lock .
     */
    public SimpleFileLock(){
        this("DotaznikServer.lock");
    }
    
    /**
     * Vytvoří instanci, zamykající soubor lockFileName. Jeho obsah bude při zamčení smazán.
     * @param lockFileName Jméno souboru, který bude sloužit k zamykání.
     * Obvykle by neměl existovat.
     */
    public SimpleFileLock(String lockFileName){
        this.lockFile = new File(lockFileName);
    }
    
    /**
     * Vytvoří instanci, zamykající soubor lockFileName. Jeho obsah bude při zamčení smazán.
     * @param file Soubor, který bude sloužit k zamykání.
     * Obvykle by neměl existovat.
     */
    public SimpleFileLock(File file){
        this.lockFile = file;
    }
    
    /**
     * Zkusí vytvořit zámek na dané adrese. Spolu s tím registruje 
     * {@link Runtime#addShutdownHook(java.lang.Thread) ShutdownHook},
     * který při ukončení aplikace automaticky odemyká zámek.
     * @return true pokud se podařilo zamknout; false jinak (tj. jiný zámek už existuje)
     * @throws IOException pokud se zámek nepodařilo vytvořit z jiného důvodu, než že již existuje.
     */
    public boolean tryLock() throws IOException{
        
        try{
            channel = new RandomAccessFile(lockFile, "rw").getChannel();
            lock = channel.tryLock();
        }catch(IOException e){
            return false;
        }
        if(lock == null){
            channel.close();
            channel = null;
            return false;
        }
        this.unlockThread = new UnlockThread();
        Runtime.getRuntime().addShutdownHook(unlockThread);
        return lock.isValid();
    }
    
    /**
     * Pokud byl zámek zamčený, odemkne ho.
     * Zároveň odstraní {@link Runtime#addShutdownHook(java.lang.Thread) ShutdownHook}
     * vytvořený pomocí {@link SimpleFileLock#tryLock() tryLock()}.
     */
    public void unlock(){
        this.unlock(true);
    }
    
    private void unlock(boolean removeHook){
        if(isLocked()){
            try{
                lock.release();
                channel.close();
                lockFile.delete();
                lock = null;
                channel = null;
            }catch(IOException e){}
        }
        if(removeHook){
            Runtime.getRuntime().removeShutdownHook(unlockThread);
        }
    }
    
    /**
     * Vrací true, pokud je zámek zamčený, jinak false.
     * @return Vrací true, pokud je zámek zamčený, jinak false.
     */
    public boolean isLocked(){
        if(lock == null){
            return false;
        }
        return lock.isValid();
    }
    
    private class UnlockThread extends Thread{

        @Override
        public void run() {
            unlock(false);
        }
        
    }
    
    private UnlockThread unlockThread;
    private File lockFile;
    private FileLock lock;
    private FileChannel channel;
}
